From cea8ab86837355b099d5445e39f8c2e46e41d746 Mon Sep 17 00:00:00 2001 From: Roger Floriano <31597636+petruki@users.noreply.github.com> Date: Sun, 12 May 2024 15:55:33 -0700 Subject: [PATCH] Standardized callback for loadSnapshot and watchSnapshot (#189) * Standardized callback for loadSnapshot and watchSnapshot * chore: fixed test async when success * Fixed scheduleSnapshotAutoUpdate callback --- README.md | 14 +++++++---- src/client.d.ts | 29 +++++++++++++++++------ src/client.js | 25 +++++++++++++------- src/lib/bypasser/key.js | 35 ++++++++++++++++------------ src/lib/utils/snapshotAutoUpdater.js | 12 ++++------ test/playground/index.js | 22 +++++++++-------- test/switcher-snapshot.test.js | 22 +++++++---------- test/switcher-watch-snapshot.test.js | 22 +++++++++-------- 8 files changed, 105 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index 44f1d9b..1cc20da 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ https://github.com/switcherapi/switcher-api The context properties stores all information regarding connectivity. ```js -import { Switcher } from 'switcher-client'; +import { Client } from 'switcher-client'; const apiKey = '[API_KEY]'; const environment = 'default'; @@ -200,9 +200,10 @@ Client.loadSnapshot(); Activate and monitor snapshot changes using this feature. Optionally, you can implement any action based on the callback response. ```js -Client.watchSnapshot( - () => console.log('In-memory snapshot updated'), - (err) => console.log(err)); +Client.watchSnapshot({ + success: () => console.log('In-memory snapshot updated'), + reject: (err) => console.log(err) +}); ``` ## Snapshot version check @@ -217,5 +218,8 @@ You can also schedule a snapshot update using the method below.
It allows you to run the Client SDK in local mode (zero latency) and still have the snapshot updated automatically. ```js -Client.scheduleSnapshotAutoUpdate(1 * 60 * 60 * 24); // 24 hours +Client.scheduleSnapshotAutoUpdate(3000, { + success: (updated) => console.log('Snapshot updated', updated), + reject: (err) => console.log(err) +}); ``` \ No newline at end of file diff --git a/src/client.d.ts b/src/client.d.ts index 8cef2a0..1414b89 100644 --- a/src/client.d.ts +++ b/src/client.d.ts @@ -5,7 +5,7 @@ import { Switcher } from './switcher'; * * 1. Use Client.buildContext() to define the arguments to connect to the API. * 2. Use Client.getSwitcher() to create a new instance of Switcher. -* 3. Use the instance created to call isItOn to query the API. +* 3. Use the instance created to call isItOn to execute criteria evaluation. */ export class Client { @@ -22,11 +22,8 @@ export class Client { /** * Read snapshot and load it into memory - * - * @param watchSnapshot when true, it will watch for snapshot file modifications - * @param fetchRemote when true, it will initialize the snapshot from the API */ - static loadSnapshot(watchSnapshot?: boolean, fetchRemote?: boolean): Promise; + static loadSnapshot(options?: LoadSnapshotOptions): Promise; /** * Verifies if the current snapshot file is updated. @@ -42,8 +39,12 @@ export class Client { * building context * * @param interval in ms + * @param success returns true if snapshot has been updated */ - static scheduleSnapshotAutoUpdate(interval?: number, callback?: (updated: boolean, err: Error) => void): void; + static scheduleSnapshotAutoUpdate(interval?: number, callback?: { + success?: (updated: boolean) => void; + reject?: (err: Error) => void; + }): void; /** * Terminates Snapshot Auto Update @@ -64,7 +65,10 @@ export class Client { * @param success when snapshot has successfully updated * @param error when any error has thrown when attempting to load snapshot */ - static watchSnapshot(success?: () => void, error?: (err: any) => void): void; + static watchSnapshot(callback: { + success?: () => void | Promise; + reject?: (err: Error) => void; + }): void; /** * Terminate watching snapshot files @@ -160,6 +164,17 @@ export type SwitcherOptions = { certPath?: string; } +/** + * LoadSnapshotOptions defines the options to load a snapshot. + * + * @param watchSnapshot when true, it will watch for snapshot file modifications + * @param fetchRemote when true, it will initialize the snapshot from the API + */ +export type LoadSnapshotOptions = { + watchSnapshot?: boolean; + fetchRemote?: boolean; +} + declare class Key { constructor(key: string); diff --git a/src/client.js b/src/client.js index 40356c1..5c678a3 100644 --- a/src/client.js +++ b/src/client.js @@ -119,27 +119,29 @@ export class Client { return false; } - static async loadSnapshot(watchSnapshot = false, fetchRemote = false) { + static async loadSnapshot(options = { fetchRemote: false, watchSnapshot: false }) { Client.#snapshot = loadDomain( util.get(Client.#options.snapshotLocation, ''), util.get(Client.#context.environment, DEFAULT_ENVIRONMENT) ); if (Client.#snapshot.data.domain.version == 0 && - (fetchRemote || !Client.#options.local)) { + (options.fetchRemote || !Client.#options.local)) { await Client.checkSnapshot(); } - if (watchSnapshot) { + if (options.watchSnapshot) { Client.watchSnapshot(); } return Client.#snapshot?.data.domain.version || 0; } - static watchSnapshot(success = () => {}, error = () => {}) { + static watchSnapshot(callback = {}) { + const { success = () => {}, reject = () => {} } = callback; + if (Client.testEnabled || !Client.#options.snapshotLocation?.length) { - return error(new Error('Watch Snapshot cannot be used in test mode or without a snapshot location')); + return reject(new Error('Watch Snapshot cannot be used in test mode or without a snapshot location')); } const snapshotFile = `${Client.#options.snapshotLocation}/${Client.#context.environment}.json`; @@ -151,7 +153,7 @@ export class Client { success(); } } catch (e) { - error(e); + reject(e); } finally { lastUpdate = listener.ctime; } @@ -168,13 +170,20 @@ export class Client { unwatchFile(snapshotFile); } - static scheduleSnapshotAutoUpdate(interval, callback) { + static scheduleSnapshotAutoUpdate(interval, callback = {}) { + const { success = () => {}, reject = () => {} } = callback; + if (interval) { Client.#options.snapshotAutoUpdateInterval = interval; } if (Client.#options.snapshotAutoUpdateInterval && Client.#options.snapshotAutoUpdateInterval > 0) { - SnapshotAutoUpdater.schedule(Client.#options.snapshotAutoUpdateInterval, this.checkSnapshot, callback); + SnapshotAutoUpdater.schedule( + Client.#options.snapshotAutoUpdateInterval, + this.checkSnapshot, + success, + reject + ); } } diff --git a/src/lib/bypasser/key.js b/src/lib/bypasser/key.js index 2fd3c2c..6e670a5 100644 --- a/src/lib/bypasser/key.js +++ b/src/lib/bypasser/key.js @@ -1,21 +1,26 @@ /** - * Type definition for Switcher Keys which are used to mock results + * Key record used to store key response when bypassing criteria execution */ export default class Key { + + #key; + #result; + #reason; + #metadata; constructor(key) { - this._key = key; - this._result = undefined; - this._reason = undefined; - this._metadata = undefined; + this.#key = key; + this.#result = undefined; + this.#reason = undefined; + this.#metadata = undefined; } /** * Force result to true */ true() { - this._result = true; - this._reason = 'Forced to true'; + this.#result = true; + this.#reason = 'Forced to true'; return this; } @@ -23,8 +28,8 @@ export default class Key { * Force result to false */ false() { - this._result = false; - this._reason = 'Forced to false'; + this.#result = false; + this.#reason = 'Forced to false'; return this; } @@ -32,7 +37,7 @@ export default class Key { * Define metadata for the response */ withMetadata(metadata) { - this._metadata = metadata; + this.#metadata = metadata; return this; } @@ -40,7 +45,7 @@ export default class Key { * Return selected switcher name */ getKey() { - return this._key; + return this.#key; } /** @@ -48,10 +53,10 @@ export default class Key { */ getResponse() { return { - key: this._key, - result: this._result, - reason: this._reason, - metadata: this._metadata + key: this.#key, + result: this.#result, + reason: this.#reason, + metadata: this.#metadata }; } } \ No newline at end of file diff --git a/src/lib/utils/snapshotAutoUpdater.js b/src/lib/utils/snapshotAutoUpdater.js index d782d26..c682b54 100644 --- a/src/lib/utils/snapshotAutoUpdater.js +++ b/src/lib/utils/snapshotAutoUpdater.js @@ -1,7 +1,7 @@ export default class SnapshotAutoUpdater { static _worker = undefined; - static schedule(interval, checkSnapshot, callback) { + static schedule(interval, checkSnapshot, success, reject) { if (this._worker) { this.terminate(); } @@ -9,14 +9,10 @@ export default class SnapshotAutoUpdater { this._worker = setInterval(async () => { try { const updated = await checkSnapshot(); - if (callback) { - callback(updated); - } + success(updated); } catch (err) { - if (callback) { - this.terminate(); - callback(null, err); - } + this.terminate(); + reject(err); } }, interval * 1000); } diff --git a/test/playground/index.js b/test/playground/index.js index 7335bcb..21a8b8c 100644 --- a/test/playground/index.js +++ b/test/playground/index.js @@ -17,7 +17,7 @@ const snapshotLocation = './test/playground/snapshot/'; */ async function setupSwitcher(local) { Client.buildContext({ url, apiKey, domain, component, environment }, { local, logger: true }); - await Client.loadSnapshot(false, local) + await Client.loadSnapshot({ watchSnapshot: false, fetchRemote: local }) .then(version => console.log('Snapshot loaded - version:', version)) .catch(() => console.log('Failed to load Snapshot')); } @@ -131,15 +131,16 @@ const _testBypasser = async () => { // Requires remote API const _testWatchSnapshot = async () => { Client.buildContext({ url, apiKey, domain, component, environment }, { snapshotLocation, local: true, logger: true }); - await Client.loadSnapshot(false, true) + await Client.loadSnapshot({ watchSnapshot: false, fetchRemote: true }) .then(() => console.log('Snapshot loaded')) .catch(() => console.log('Failed to load Snapshot')); const switcher = Client.getSwitcher(); - - Client.watchSnapshot( - async () => console.log('In-memory snapshot updated', await switcher.isItOn(SWITCHER_KEY)), - (err) => console.log(err)); + + Client.watchSnapshot({ + success: async () => console.log('In-memory snapshot updated', await switcher.isItOn(SWITCHER_KEY)), + reject: (err) => console.log(err) + }); }; // Requires remote API @@ -147,12 +148,13 @@ const _testSnapshotAutoUpdate = async () => { Client.buildContext({ url, apiKey, domain, component, environment }, { local: true, logger: true }); - await Client.loadSnapshot(false, true); + await Client.loadSnapshot({ watchSnapshot: false, fetchRemote: true }); const switcher = Client.getSwitcher(); - Client.scheduleSnapshotAutoUpdate(3, - (updated) => console.log('In-memory snapshot updated', updated), - (err) => console.log(err)); + Client.scheduleSnapshotAutoUpdate(1, { + success: (updated) => console.log('In-memory snapshot updated', updated), + reject: (err) => console.log(err) + }); setInterval(async () => { const time = Date.now(); diff --git a/test/switcher-snapshot.test.js b/test/switcher-snapshot.test.js index 138cf5f..655a64f 100644 --- a/test/switcher-snapshot.test.js +++ b/test/switcher-snapshot.test.js @@ -56,7 +56,7 @@ describe('E2E test - Switcher local - Snapshot:', function () { regexSafe: false }); - await Client.loadSnapshot(true); + await Client.loadSnapshot({ watchSnapshot: true }); assert.isTrue(await Client.checkSnapshot()); //restore state to avoid process leakage @@ -79,7 +79,7 @@ describe('E2E test - Switcher local - Snapshot:', function () { regexSafe: false }); - await Client.loadSnapshot(true); + await Client.loadSnapshot({ watchSnapshot: true }); assert.isTrue(await Client.checkSnapshot()); assert.isTrue(existsSync(`generated-snapshots/${environment}.json`)); @@ -102,7 +102,7 @@ describe('E2E test - Switcher local - Snapshot:', function () { regexSafe: false }); - await Client.loadSnapshot(true, true); + await Client.loadSnapshot({ watchSnapshot: true, fetchRemote: true }); assert.isTrue(existsSync(`generated-snapshots/${environment}.json`)); //restore state to avoid process leakage @@ -328,13 +328,11 @@ describe('E2E test - Snapshot AutoUpdater:', function () { }); let snapshotUpdated = false; - Client.scheduleSnapshotAutoUpdate(1, (updated) => { - if (updated != undefined) { - snapshotUpdated = updated; - } + Client.scheduleSnapshotAutoUpdate(1, { + success: (updated) => snapshotUpdated = updated }); - await Client.loadSnapshot(false, true); + await Client.loadSnapshot({ watchSnapshot: false, fetchRemote: true }); const switcher = Client.getSwitcher(); assert.isFalse(await switcher.isItOn('FF2FOR2030')); @@ -361,13 +359,11 @@ describe('E2E test - Snapshot AutoUpdater:', function () { }); let error; - Client.scheduleSnapshotAutoUpdate(1, (updated, err) => { - if (err != undefined) { - error = err; - } + Client.scheduleSnapshotAutoUpdate(1, { + reject: (err) => error = err }); - await Client.loadSnapshot(false, true); + await Client.loadSnapshot({ watchSnapshot: false, fetchRemote: true }); //next call will fail givenError(fetchStub, 3, { errno: 'ECONNREFUSED' }); diff --git a/test/switcher-watch-snapshot.test.js b/test/switcher-watch-snapshot.test.js index 51d9f3c..09d1f9c 100644 --- a/test/switcher-watch-snapshot.test.js +++ b/test/switcher-watch-snapshot.test.js @@ -79,10 +79,12 @@ describe('E2E test - Switcher local - Watch Snapshot:', function () { initContext('watch2').then(() => { const switcher = Client.getSwitcher(); - Client.watchSnapshot(async () => { - const result = await switcher.isItOn('FF2FOR2030'); - assert.isFalse(result); - done(); + Client.watchSnapshot({ + success: async () => { + const result = await switcher.isItOn('FF2FOR2030'); + assert.isFalse(result); + done(); + } }); setTimeout(async () => { @@ -98,9 +100,11 @@ describe('E2E test - Switcher local - Watch Snapshot:', function () { initContext('watch3').then(() => { const switcher = Client.getSwitcher(); - Client.watchSnapshot(undefined, (err) => { - assert.equal(err.message, 'Something went wrong: It was not possible to load the file at generated-watch-snapshots/'); - done(); + Client.watchSnapshot({ + reject: (err) => { + assert.equal(err.message, 'Something went wrong: It was not possible to load the file at generated-watch-snapshots/'); + done(); + } }); setTimeout(() => { @@ -118,9 +122,7 @@ describe('E2E test - Switcher local - Watch Snapshot:', function () { Client.testMode(); let errorMessage; - Client.watchSnapshot(undefined, (err) => { - errorMessage = err.message; - }); + Client.watchSnapshot({ reject: (err) => errorMessage = err.message }); assert.equal(errorMessage, 'Watch Snapshot cannot be used in test mode or without a snapshot location'); done();