Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,20 +63,22 @@ const local = true;
const logger = true;
const snapshotLocation = './snapshot/';
const snapshotAutoUpdateInterval = 3;
const snapshotWatcher = true;
const silentMode = '5m';
const certPath = './certs/ca.pem';

Client.buildContext({ url, apiKey, domain, component, environment }, {
local, logger, snapshotLocation, snapshotAutoUpdateInterval, silentMode, certPath
local, logger, snapshotLocation, snapshotAutoUpdateInterval, snapshotWatcher, silentMode, certPath
});

let switcher = Client.getSwitcher();
const switcher = Client.getSwitcher();
```

- **local**: If activated, the client will only fetch the configuration inside your snapshot file. The default value is 'false'
- **logger**: If activated, it is possible to retrieve the last results from a given Switcher key using Client.getLogger('KEY')
- **snapshotLocation**: Location of snapshot files. The default value is './snapshot/'
- **snapshotAutoUpdateInterval**: Enable Snapshot Auto Update given an interval in seconds (default: 0 disabled).
- **snapshotWatcher**: Enable Snapshot Watcher to monitor changes in the snapshot file (default: false).
- **silentMode**: Enable contigency given the time for the client to retry - e.g. 5s (s: seconds - m: minutes - h: hours)
- **regexSafe**: Enable REGEX Safe mode - Prevent agaist reDOS attack (default: true).
- **regexMaxBlackList**: Number of entries cached when REGEX Strategy fails to perform (reDOS safe) - default: 50
Expand Down Expand Up @@ -212,6 +214,16 @@ Client.watchSnapshot({
});
```

Alternatively, you can also use the client context configuration to monitor changes in the snapshot file.<br>

```js
Client.buildContext({ domain, component, environment }, {
local: true,
snapshotLocation: './snapshot/',
snapshotWatcher: true
});
```

## Snapshot version check
For convenience, an implementation of a domain version checker is available if you have external processes that manage snapshot files.

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
],
"devDependencies": {
"@babel/eslint-parser": "^7.27.5",
"@typescript-eslint/eslint-plugin": "^8.34.1",
"@typescript-eslint/parser": "^8.34.1",
"@typescript-eslint/eslint-plugin": "^8.35.0",
"@typescript-eslint/parser": "^8.35.0",
"c8": "^10.1.3",
"chai": "^5.2.0",
"env-cmd": "^10.1.0",
Expand Down
77 changes: 76 additions & 1 deletion src/client.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export type Criteria = {
*
* @param strategy (StrategiesType) value to be evaluated
*/
and(strategy: string, input: string | string[]): this;
and(strategy: string, input: string | string[]): Criteria;

}

Expand All @@ -156,25 +156,93 @@ export type LoggerRecord = {
* SwitcherContext is required to build the context to communicate with the API.
*/
export type SwitcherContext = {
/**
* The API URL, e.g. https://api.switcherapi.com
*/
url?: string;

/**
* The API key provided for the component
*/
apiKey?: string;

/**
* The domain name of the Switcher API account
*/
domain: string;

/**
* The component name registered in the Switcher API account
*/
component?: string;

/**
* The environment name registered in the Switcher API account
*/
environment?: string;
}

/**
* SwitcherOptions is optional to build the context to communicate with the API.
*/
export type SwitcherOptions = {
/**
* When enabled it will use the local snapshot (file or in-memory)
* If not set, it will use the remote API
*/
local?: boolean;

/**
* When enabled it allows inspecting the result details with Client.getLogger(key)
* If not set, it will not log the result details
*/
logger?: boolean;

/**
* The location of the snapshot file
* If not set, it will use the in-memory snapshot
*/
snapshotLocation?: string;

/**
* The interval in milliseconds to auto-update the snapshot
* If not set, it will not auto-update the snapshot
*/
snapshotAutoUpdateInterval?: number;

/**
* When enabled it will watch the snapshot file for changes
*/
snapshotWatcher?: boolean;

/**
* When defined it will switch to local during the specified time before it switches back to remote
* e.g. 5s (s: seconds - m: minutes - h: hours)
*/
silentMode?: string;

/**
* When enabled it will check Regex strategy using background workers
* If not set, it will check Regex strategy synchronously
*/
regexSafe?: boolean;

/**
* The regex max black list
* If not set, it will use the default value
*/
regexMaxBlackList?: number;

/**
* The regex max time limit in milliseconds
* If not set, it will use the default value
*/
regexMaxTimeLimit?: number;

/**
* The certificate path for secure connections
* If not set, it will use the default certificate
*/
certPath?: string;
}

Expand All @@ -185,7 +253,14 @@ export type SwitcherOptions = {
* @param fetchRemote when true, it will initialize the snapshot from the API
*/
export type LoadSnapshotOptions = {
/**
* When enabled it will watch the snapshot file for changes
*/
watchSnapshot?: boolean;

/**
* When enabled it will fetch the remote API
*/
fetchRemote?: boolean;
}

Expand Down
29 changes: 18 additions & 11 deletions src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,25 @@ export class Client {

static #buildOptions(options) {
remote.removeAgent();
if (SWITCHER_OPTIONS.CERT_PATH in options && options.certPath) {
remote.setCerts(options.certPath);
}

if (SWITCHER_OPTIONS.SILENT_MODE in options && options.silentMode) {
this.#initSilentMode(options.silentMode);
}

const optionsHandler = {
[SWITCHER_OPTIONS.CERT_PATH]: (val) => val && remote.setCerts(val),
[SWITCHER_OPTIONS.SILENT_MODE]: (val) => val && this.#initSilentMode(val),
[SWITCHER_OPTIONS.SNAPSHOT_AUTO_UPDATE_INTERVAL]: (val) => {
GlobalOptions.updateOptions({ snapshotAutoUpdateInterval: val });
this.scheduleSnapshotAutoUpdate();
},
[SWITCHER_OPTIONS.SNAPSHOT_WATCHER]: (val) => {
GlobalOptions.updateOptions({ snapshotWatcher: val });
this.watchSnapshot();
}
};

if (SWITCHER_OPTIONS.SNAPSHOT_AUTO_UPDATE_INTERVAL in options) {
GlobalOptions.updateOptions({ snapshotAutoUpdateInterval: options.snapshotAutoUpdateInterval });
this.scheduleSnapshotAutoUpdate();
}
Object.entries(optionsHandler).forEach(([key, handler]) => {
if (key in options) {
handler(options[key]);
}
});

this.#initTimedMatch(options);
}
Expand Down
1 change: 1 addition & 0 deletions src/lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const DEFAULT_REGEX_MAX_TIME_LIMIT = 3000;
export const SWITCHER_OPTIONS = Object.freeze({
SNAPSHOT_LOCATION: 'snapshotLocation',
SNAPSHOT_AUTO_UPDATE_INTERVAL: 'snapshotAutoUpdateInterval',
SNAPSHOT_WATCHER: 'snapshotWatcher',
SILENT_MODE: 'silentMode',
REGEX_SAFE: 'regexSafe',
REGEX_MAX_BLACK_LIST: 'regexMaxBlackList',
Expand Down
15 changes: 15 additions & 0 deletions test/helper/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,21 @@ export async function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

export async function getSwitcherResulUntil(switcher, key, expectToBe, retries = 20) {
let result;

for (let i = 0; i < retries; i++) {
await sleep(500);
result = await switcher.isItOn(key);

if (result === expectToBe) {
break;
}
}

return result;
};

export function deleteGeneratedSnapshot(dirname) {
if (!fs.existsSync(dirname)) {
return;
Expand Down
24 changes: 24 additions & 0 deletions test/playground/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ const _testAsyncCall = async () => {
Client.unloadSnapshot();
};

// Does not require remote API
const _testBypasser = async () => {
setupSwitcher(true);
const switcher = Client.getSwitcher();
Expand Down Expand Up @@ -140,6 +141,29 @@ const _testWatchSnapshot = async () => {
});
};

// Does not require remote API
const _testWatchSnapshotContextOptions = async () => {
Client.buildContext({ domain, environment }, {
snapshotLocation,
snapshotWatcher: true,
local: true,
logger: true
});

await Client.loadSnapshot();

const switcher = Client.getSwitcher();

setInterval(async () => {
const time = Date.now();
const result = await switcher
.detail()
.isItOn(SWITCHER_KEY);

console.log(`- ${Date.now() - time} ms - ${JSON.stringify(result)}`);
}, 1000);
};

// Requires remote API
const _testSnapshotAutoUpdate = async () => {
Client.buildContext({ url, apiKey, domain, component, environment },
Expand Down
Loading