diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 93278f7..ee1ed6b 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -17,10 +17,10 @@ jobs: with: fetch-depth: 0 - - name: Setup Deno v2.7.13 + - name: Setup Deno v2.7.14 uses: denoland/setup-deno@v2 with: - deno-version: v2.7.13 + deno-version: v2.7.14 - name: Setup LCOV run: sudo apt install -y lcov @@ -35,7 +35,7 @@ jobs: run: deno task cover - name: SonarCloud Scan - uses: sonarsource/sonarqube-scan-action@v7.0.0 + uses: sonarsource/sonarqube-scan-action@v8.0.0 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} if: env.SONAR_TOKEN != '' @@ -45,7 +45,7 @@ jobs: strategy: fail-fast: false matrix: - deno-version: [v1.46.3, v2.7.13] + deno-version: [v1.46.3, v2.7.14] os: [ ubuntu-latest, windows-latest ] runs-on: ${{ matrix.os }} diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 80ec08a..0e2e1c4 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -33,10 +33,10 @@ jobs: ref: ${{ steps.pr.outputs.head_sha }} fetch-depth: 0 - - name: Setup Deno v2.7.13 + - name: Setup Deno v2.7.14 uses: denoland/setup-deno@v2 with: - deno-version: v2.7.13 + deno-version: v2.7.14 - name: Setup LCOV run: sudo apt install -y lcov @@ -51,7 +51,7 @@ jobs: run: deno task cover - name: SonarCloud Scan - uses: sonarsource/sonarqube-scan-action@v7.0.0 + uses: sonarsource/sonarqube-scan-action@v8.0.0 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} if: env.SONAR_TOKEN != '' diff --git a/README.md b/README.md index f1ea67f..138c8cc 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,8 @@ Client.buildContext({ silentMode: '5m', // Fallback timeout restrictRelay: true, // Relay restrictions in local mode regexSafe: true, // Prevent reDOS attacks - certPath: './certs/ca.pem' // SSL certificate path + certPath: './certs/ca.pem', // SSL certificate path + remoteTimeout: 2000 // Remote Criteria API timeout (milliseconds) }); ``` @@ -153,6 +154,7 @@ Client.buildContext({ | `regexMaxBlackList` | number | Max cached regex failures | | `regexMaxTimeLimit` | number | Regex timeout in milliseconds | | `certPath` | string | Path to SSL certificate file | +| `remoteTimeout` | number | Remote Criteria API timeout in milliseconds | > **⚠️ Note on regexSafe**: This feature protects against reDOS attacks but uses Web Workers, which are incompatible with compiled executables. diff --git a/src/client.ts b/src/client.ts index e93671e..31ba552 100644 --- a/src/client.ts +++ b/src/client.ts @@ -68,12 +68,12 @@ export class Client { logger: util.get(options?.logger, DEFAULT_LOGGER), }); + // Initialize Auth + Auth.init(this._context); + if (options) { Client.buildOptions(options); } - - // Initialize Auth - Auth.init(this._context); } private static buildOptions(options: SwitcherOptions) { @@ -84,6 +84,9 @@ export class Client { [SWITCHER_OPTIONS.SILENT_MODE]: () => { if (options.silentMode) this._initSilentMode(options.silentMode); }, + [SWITCHER_OPTIONS.REMOTE_TIMEOUT]: () => { + if (options.remoteTimeout) remote.setRemoteTimeout(options.remoteTimeout); + }, [SWITCHER_OPTIONS.RESTRICT_RELAY]: () => { GlobalOptions.updateOptions({ restrictRelay: options.restrictRelay }); }, @@ -371,7 +374,7 @@ export class Client { /** * Enable/Disable test mode. * - * It prevents from watching Snapshots that may hold process + * It prevents subprocess to run during tests such as snapshot watcher */ static testMode(testEnabled: boolean = true): void { Client._testEnabled = testEnabled; diff --git a/src/lib/bypasser/index.ts b/src/lib/bypasser/index.ts index da134b9..fd2b02e 100644 --- a/src/lib/bypasser/index.ts +++ b/src/lib/bypasser/index.ts @@ -9,10 +9,7 @@ export default class Bypasser { * @param key */ static assume(key: string): Key { - const existentKey = this.searchBypassed(key); - if (existentKey) { - return existentKey; - } + Bypasser.forget(key); const keyBypassed = new Key(key); bypassedKeys.push(keyBypassed); diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 54bfea1..6306700 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -16,4 +16,5 @@ export enum SWITCHER_OPTIONS { REGEX_MAX_BLACK_LIST = 'regexMaxBlackList', REGEX_MAX_TIME_LIMIT = 'regexMaxTimeLimit', CERT_PATH = 'certPath', + REMOTE_TIMEOUT = 'remoteTimeout', } diff --git a/src/lib/remote.ts b/src/lib/remote.ts index fe919f2..c053164 100644 --- a/src/lib/remote.ts +++ b/src/lib/remote.ts @@ -5,6 +5,7 @@ import * as util from './utils/index.ts'; import { GlobalAuth } from './globals/globalAuth.ts'; let httpClient: Deno.HttpClient; +let remoteTimeout: number = 2 * 1000; const getHeader = (token: string | undefined) => { return { @@ -20,6 +21,10 @@ export const setCerts = (certPath: string) => { }); }; +export const setRemoteTimeout = (timeout: number) => { + remoteTimeout = timeout; +}; + export const destroyHttpClient = () => { if (httpClient) { httpClient.close(); @@ -89,6 +94,7 @@ export const checkCriteria = async ( { client: httpClient, method: 'post', + signal: AbortSignal.timeout(remoteTimeout), body: JSON.stringify({ entry }), headers: getHeader(GlobalAuth.token), }, diff --git a/src/types/index.d.ts b/src/types/index.d.ts index c03244c..3beb647 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -115,6 +115,13 @@ export type SwitcherOptions = { * If not set, it will use the default certificate */ certPath?: string; + + /** + * The remote timeout in milliseconds for crieteria evaluation API calls + * + * If not set, it will use the default value (2000 ms) + */ + remoteTimeout?: number; }; export type RetryOptions = { diff --git a/tests/switcher-context.test.ts b/tests/switcher-context.test.ts index 5d38768..cf3bc16 100644 --- a/tests/switcher-context.test.ts +++ b/tests/switcher-context.test.ts @@ -49,6 +49,11 @@ describe('Switcher Context:', function () { Error, 'Something went wrong: URL is required'); }); + it('should NOT throw when remoteTimeout is set', function() { + Client.buildContext(contextSettings, { remoteTimeout: 5000 }); + assertTrue(true); + }); + it('should be invalid - Missing API Key field', async function () { // given given('POST@/criteria/auth', generateAuth('[auth_token]', 5));