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();